import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.swing.*;
import javax.swing.JTree.DynamicUtilTreeNode;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.time.DynamicTimeSeriesCollection;
import org.jfree.data.time.Second;
import org.jfree.data.xy.XYDataset;

public class MonitorView extends JFrame
{
	private JPanel m_agentsPanel;
	private JPanel m_statisticPanel;
	private JPanel m_graphPanel;
	private JTree m_agentsTree;
	//private Vector<Object>  m_treeRoot;
	private JTable m_table;
	private TableModel m_tableModel;
	private List<IRegisterRequestListener> m_registerRequestListeners;
	private Map<String,DynamicTimeSeriesCollection> m_datasetByKey;
	
	private AgentTreeModel m_treeModel;

	public MonitorView()
	{
		super();
		setLayout(new GridBagLayout());
		
		// Initialize data structures
		m_datasetByKey = new HashMap<String, DynamicTimeSeriesCollection>();
		m_registerRequestListeners = new ArrayList<IRegisterRequestListener>();
		
		// Create agents panel
		m_agentsPanel = new JPanel(new GridBagLayout());
		m_agentsPanel.setAlignmentX(LEFT_ALIGNMENT);
		m_agentsPanel.setBorder(BorderFactory.createTitledBorder("Agents"));
		GridBagConstraints agentsPanelConst = new GridBagConstraints();
		agentsPanelConst.anchor = GridBagConstraints.NORTHWEST;
		agentsPanelConst.fill = GridBagConstraints.BOTH;
		agentsPanelConst.gridx=0;
		agentsPanelConst.gridy=0;
		agentsPanelConst.gridheight=GridBagConstraints.REMAINDER;
		agentsPanelConst.weightx=1.5;
		agentsPanelConst.weighty=5;
		add(m_agentsPanel,agentsPanelConst);
		m_agentsPanel.setPreferredSize(
				new Dimension(m_agentsPanel.getWidth(),m_agentsPanel.getHeight()));
		
		// Create Statistics panel
		m_statisticPanel = new JPanel(new GridBagLayout());
		m_statisticPanel.setBorder(BorderFactory.createTitledBorder("Monitored data"));
		GridBagConstraints statPanelConst = new GridBagConstraints();
		statPanelConst.anchor = GridBagConstraints.NORTHWEST;
		statPanelConst.fill = GridBagConstraints.BOTH;
		statPanelConst.gridx=1;
		statPanelConst.gridy=1;
		statPanelConst.weightx=8;
		statPanelConst.weighty=5;
		add(m_statisticPanel,statPanelConst);
		m_statisticPanel.setPreferredSize(
				new Dimension(m_statisticPanel.getWidth(),m_statisticPanel.getHeight()));
		
		// Create graph panel
		m_graphPanel = new JPanel(new GridBagLayout());
		m_graphPanel.setBorder(BorderFactory.createTitledBorder("Graph"));
		GridBagConstraints graphPanelConst = new GridBagConstraints();
		graphPanelConst.anchor = GridBagConstraints.NORTHWEST;
		graphPanelConst.fill = GridBagConstraints.BOTH;
		graphPanelConst.gridx=1;
		graphPanelConst.gridy=0;
		graphPanelConst.weightx=8;
		graphPanelConst.weighty=5;
		add(m_graphPanel,graphPanelConst);
		m_graphPanel.setPreferredSize(
				new Dimension(m_graphPanel.getWidth(),m_graphPanel.getHeight()));

		// Create table
		GridBagConstraints tableConstr = new GridBagConstraints();
		tableConstr.anchor = GridBagConstraints.NORTHWEST;
		tableConstr.fill = GridBagConstraints.BOTH;
		tableConstr.gridx=1;
		tableConstr.gridy=1;
		tableConstr.weightx=1;
		tableConstr.weighty=4;
		m_tableModel = new TableModel();
        m_table = new JTable(m_tableModel);
        m_table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        m_table.getColumn("Value").setCellRenderer(new ProgressCellRender());
        StatusTableCellRenderer cellRenderer = new StatusTableCellRenderer();
        m_table.getColumn("Host").setCellRenderer(cellRenderer);
        m_table.getColumn("IP").setCellRenderer(cellRenderer);
        m_table.getColumn("Parameter").setCellRenderer(cellRenderer);
        m_table.getColumn("Monitor Type").setCellRenderer(cellRenderer);
        m_table.getSelectionModel().addListSelectionListener(new ListSelectionListener()
        {
        	@Override
        	public void valueChanged(ListSelectionEvent e) 
        	{
        		HandleTableSelection(e);
        	}});
        
        m_statisticPanel.add(new JScrollPane(m_table),tableConstr);
		
		GridBagConstraints treeConst = new GridBagConstraints();
		treeConst.anchor = GridBagConstraints.NORTHWEST;
		treeConst.fill = GridBagConstraints.BOTH;
		treeConst.gridx=0;
		treeConst.gridy=0;
		treeConst.gridheight=2;
		treeConst.weightx=1;
		treeConst.weighty=1;
		
		//m_treeRoot = new TreeNodeVector<Object>("Agents");
		RootNode root = new RootNode("Agents");
		
		CheckBoxNodeRenderer renderer = new CheckBoxNodeRenderer();
		m_treeModel = new AgentTreeModel(root);
		m_agentsTree = new JTree(m_treeModel);
		m_agentsTree.setCellRenderer(renderer);
		m_agentsTree.setCellEditor(new CheckBoxNodeEditor(m_agentsTree));
		m_agentsTree.setEditable(true);
		m_agentsTree.addMouseListener(new MouseAdapter() 
			{
		      public void mouseClicked(MouseEvent me) 
		      {
		    	  HandleTreeMonitorDataSelection(me);
		      }
		    });
		m_agentsTree.setRootVisible(false);
		m_agentsPanel.add(new JScrollPane(m_agentsTree), treeConst);
	}

	private JFreeChart createChart(String key, final XYDataset dataset) 
    {
        final JFreeChart result = ChartFactory.createTimeSeriesChart(
        		key, "Time", "Percent", dataset, false, true, false);
        final XYPlot plot = result.getXYPlot();
        ValueAxis domain = plot.getDomainAxis();
        domain.setAutoRange(true);
        ValueAxis range = plot.getRangeAxis();
        range.setRange(0, 100);
        return result;
    }
	
	private void HandleTableSelection(ListSelectionEvent e) 
	{
		ListSelectionModel lsm = (ListSelectionModel)e.getSource();
		int index = lsm.getMinSelectionIndex();
		if(lsm.isSelectedIndex(index))
		{
			MonitorData monitorData = m_tableModel.getRow(lsm.getMinSelectionIndex());
			String key = createKey(monitorData);
			DynamicTimeSeriesCollection dataset = m_datasetByKey.get(key);
			m_graphPanel.removeAll();
			JFreeChart chart = createChart(key, dataset);
			
			GridBagConstraints graphConst = new GridBagConstraints();
			graphConst.anchor = GridBagConstraints.NORTHWEST;
			graphConst.fill = GridBagConstraints.BOTH;
			graphConst.gridx=0;
			graphConst.gridy=0;
			graphConst.weightx=8;
			graphConst.weighty=7;
			
			m_graphPanel.add(new ChartPanel(chart),graphConst);
			m_graphPanel.validate();
			m_graphPanel.repaint();
		}
	}

	public static boolean validIP(String ip) {
	    if (ip == null || ip.isEmpty()) return false;
	    ip = ip.trim();
	    if ((ip.length() < 6) & (ip.length() > 15)) return false;

	    try {
	        Pattern pattern = Pattern.compile("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
	        Matcher matcher = pattern.matcher(ip);
	        return matcher.matches();
	    } catch (PatternSyntaxException ex) {
	        return false;
	    }
	}

	private void HandleTreeMonitorDataSelection(MouseEvent me) 
	{
		TreePath tp = m_agentsTree.getPathForLocation(me.getX(), me.getY());
		if(tp == null)
			return;
		
		Object lastPathComp = tp.getLastPathComponent();
  	  	if(lastPathComp instanceof MonitorDataNode)
  	  	{
  	  		MonitorDataNode monitorDataNode = (MonitorDataNode)lastPathComp;
  	  		MonitorData data = monitorDataNode.getData();
  	  		// Add monitor data to the table
  	  		if(monitorDataNode.getIsSelected())
  	  		{
  	  			if(data.getHasParameter())
  	  			{
  	  				// Open dialog box
  	  				String s = (String)JOptionPane.showInputDialog(
  	  						this,
  	  						"Enter IP address to monitor:\n",
  	  						"Parameter needed",
  	  						JOptionPane.PLAIN_MESSAGE);
  	  				
  	  				// Check the validation of the parameter
  	  				if ((validIP(s) == true) && 
  	  					(s != null) && 
  	  					(s.length() > 0)) 
  	  				{
  	  					// Set the parameter to for the agent
  	  					data.setParameter(s);
  	  					// Add row to the table
  	  					m_tableModel.addRow(data);
  	  					// Add dataset to m_datasetByKey for the graph 
  	  					m_datasetByKey.put(createKey(data), createDataSet(data));
  	  					// Send registration manage to the agent
  	  					RegisterMonitorData(data);
  	  				}
  	  				//If you're here, the return value was null/empty.
  	  				else
  	  				{
  	  					JOptionPane.showMessageDialog(
  	  							this,
  	    	  				    "Invalid IP address, please try again.",
  	    	  				    "Error",
  	    	  				    JOptionPane.ERROR_MESSAGE);
						monitorDataNode.setIsSelected(false);
  	  				}
  	  			}
  	  			// Monitor type with no parameter
  	  			else 
  	  			{
  	  				m_tableModel.addRow(data);		
  	  				m_datasetByKey.put(createKey(data), createDataSet(data));
  	  				RegisterMonitorData(data);
  	  			}
  	  		}
  	  		// When leaf is unchecked in the tree
  	  		else
  	  		{
  	  			UnregisterMonitorData(data);
  	  			
  	  			// Get selected row in table
  	  			int selectedTableRow = m_table.getSelectedRow();
  	  			MonitorData tableSelectedData = m_tableModel.getRow(selectedTableRow);
				
				// If the unchecked item is the selected row in grid, remove the chart
				if(data.equals(tableSelectedData))
				{
					m_graphPanel.removeAll();
					m_graphPanel.revalidate();
					m_graphPanel.repaint();
				}
				
				m_tableModel.removeRow(data);
  	  			String key = createKey(data);
				m_datasetByKey.remove(key);
  	  		}
  	  	}
	}
	
	private DynamicTimeSeriesCollection createDataSet(MonitorData monitorData)
	{
		String key = createKey(monitorData);
		DynamicTimeSeriesCollection dataset = new DynamicTimeSeriesCollection(1, 120,new Second());
		dataset.setTimeBase(new Second(Calendar.getInstance().getTime()));
		dataset.addSeries(new float[]{}, 0, key);
		return dataset;
	}

	private String createKey(MonitorData data) 
	{
		return data.getIP() + "|" + data.getMonitorDataType().toString();
	}

	private void UnregisterMonitorData(MonitorData data) 
	{
		for(IRegisterRequestListener listener:m_registerRequestListeners)
		{
			listener.Unregister(data);
		}
	}

	private void RegisterMonitorData(MonitorData data) 
	{
		data.setIsRegister(true);
		for(IRegisterRequestListener listener:m_registerRequestListeners)
		{
			listener.Register(data);
		}
	}

	public void addRegisterRequestListener(IRegisterRequestListener listener)
	{
		m_registerRequestListeners.add(listener);
	}
		
	// TODO: update to IAgent interface
	public void AddAgent(Agent agent)
	{	
		AgentNode oldAgent = AddAgentToTree(agent);
		// if the agent is not new
		if(oldAgent != null)
		{
			// Re register
			for(MonitorDataNode node:oldAgent.getChilds())
			{
				if(node.getIsSelected())
				{
					RegisterMonitorData(node.getData());
				}
			}
			
			List<MonitorData> tableRows = m_tableModel.getAllRows();
			for(MonitorData row:tableRows)
			{
				if(row.getHost().equals(agent.getHost()))
				{
					row.setIsAlive(false);
					m_tableModel.updateRow(row);
				}
			}
		}
		m_agentsTree.validate();
		m_agentsTree.repaint();
	}
	
	public void HandleMonitorData(MonitorData monitorData)
	{
		m_tableModel.updateRow(monitorData);
		String key = createKey(monitorData);
		DynamicTimeSeriesCollection dataset = m_datasetByKey.get(key);
		if(dataset!= null)
		{
			dataset.advanceTime();
			dataset.appendData(new float[]{Integer.parseInt(monitorData.getValue())});
		}
	}
	
	public void HandleAgentTimeout(String host)
	{
		Collection<AgentNode> agents = m_treeModel.getRootTreeNode().getAgents();
		for(AgentNode node:agents)
		{
			if(node.getHost().equals(host))
			{
				m_treeModel.markAgentAsDown(node);
			}
		}
		
		List<MonitorData> tableRows = m_tableModel.getAllRows();
		for(MonitorData row:tableRows)
		{
			if(row.getHost().equals(host))
			{
				row.setIsAlive(false);
				m_tableModel.updateRow(row);
			}
		}
	}
	
	private AgentNode AddAgentToTree(Agent agent) 
	{	
		MonitorTypeEnum[] capArr = agent.getMonitoringCapabilities();
		AgentNode agentNode = new AgentNode(m_treeModel.getRootTreeNode(),
											agent.getHost(),agent.getIP());
		for(MonitorTypeEnum monitorType:capArr)
		{
			MonitorData monitorData = new MonitorData(agent.getIP(), agent.getHost(), monitorType);
			monitorData.setPort(agent.getPort());
			MonitorDataNode monitorDataNode = new MonitorDataNode(agentNode,monitorData);
			agentNode.add(monitorDataNode);
		}
		return m_treeModel.addAgent(agentNode);
	}
	
	public void UnregisterAllMonitorData() 
	{ 
		for(MonitorData row:m_tableModel.getAllRows()) 
		{	
	 		UnregisterMonitorData(row); 
		} 
	}
}
